#pragma once
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <stdexcept>
#include <vector>
#include <cstdarg>
#include <thread>
#include <filesystem>

#define COLOR_NONE "\033[m"
#define COLOR_RED "\033[0;32;31m"
#define COLOR_GREEN "\033[0;32;32m"
#define COLOR_YELLOW "\033[1;33m"
#define COLOR_LIGHT_BLUE "\033[1;34m"

namespace wtclog
{
    enum LogType
    {
        _none_ = 0,
        _debug_,
        _info_,
        _warning_,
        _error_,
        _end_
    };

    struct Logger
    {

        struct LogTypeDesc
        {
            std::string m_Name;
            std::string m_Color;
        };

        std::vector<LogTypeDesc> typeDescs = {
            {"none ", COLOR_NONE},
            {"debug", COLOR_GREEN},
            {"info ", COLOR_LIGHT_BLUE},
            {"warn ", COLOR_YELLOW},
            {"error", COLOR_RED}};

        enum OutType
        {
            _Console_ = 0,
            _File_ = 1
        } m_Type = OutType::_Console_;
        
        LogType m_LogLevel;

        std::string m_OutPath;

        void SetOutPath(const std::string &path)
        {
            namespace fs = std::filesystem;

            fs::path _p(path);
            if (_p.has_parent_path())
            {
                fs::create_directories(_p.parent_path());
            }

            m_OutPath = path;
        }

        void Active(const Logger::OutType &type)
        {
            m_Type = type;
        }

        template <typename... Args>
        std::string Format(const char *fmt, Args &&...args)
        {
            int size = std::snprintf(nullptr, 0, fmt, std::forward<Args>(args)...);
            if (size < 0)
            {
                throw std::runtime_error("Formatting error");
            }

            std::string result(size, '\0');
            std::snprintf(&result[0], size + 1, fmt, std::forward<Args>(args)...);

            return result;
        }

        template <typename... Args>
        void Loge(const char *fmt, Args &&...args)
        {
            __Log__(LogType::_error_, fmt, std::forward<Args>(args)...);
        };

        template <typename... Args>
        void Logw(const char *fmt, Args &&...args)
        {

            __Log__(LogType::_warning_, fmt, std::forward<Args>(args)...);
        };
        template <typename... Args>
        void Logd(const char *fmt, Args &&...args)
        {

            __Log__(LogType::_debug_, fmt, std::forward<Args>(args)...);
        };
        template <typename... Args>
        void Logi(const char *fmt, Args &&...args)
        {
            __Log__(LogType::_info_, fmt, std::forward<Args>(args)...);
        };

        template <typename... Args>
        void __Log__(const LogType &type, const char *fmt, Args &&...args)
        {

            std::string text = Format(fmt, std::forward<Args>(args)...);
            Log(type, text);
        };

        template <typename... Args>
        void Info(const LogType &type, const char *fmt, Args &&...args)
        {
            std::string text = Format(fmt, std::forward<Args>(args)...);

            if (m_LogLevel <= type)
            {
                Log(type, text);
            }
        };

        void SetLogLevel(LogType type)
        {
            m_LogLevel = type;
        }

        void Log(const LogType &type, const std::string &text)
        {
            const auto &desc = typeDescs[type];

            switch (m_Type)
            {
            case _Console_:
                Console(type, text);
                break;
            case _File_:
                FileOut(type, text);
                break;
            default:
                break;
            }
        };
        void Console(const LogType &type, const std::string &text)
        {

            const auto &desc = typeDescs[type];
            const auto &name = desc.m_Name;

            std::cout << desc.m_Color;
            std::cout << "[" << name << "] ";
            std::cout << text;
            std::cout << COLOR_NONE;
            std::cout << std::endl;
        };
        void FileOut(const LogType &type, const std::string &text)
        {

            std::ofstream ofs(m_OutPath, std::ios::app);

            const auto &desc = typeDescs[type];
            const auto &name = desc.m_Name;

            ofs << "[" << name << "] ";
            ofs << text;
            ofs << std::endl;
            ofs.flush();
        };
        static Logger &Inst()
        {
            static Logger logger;
            return logger;
        }
    };

    template <typename... Args>
    void info(const char *fmt, Args &&...args)
    {
        Logger::Inst().Info(LogType::_info_, fmt, std::forward<Args>(args)...);
    };
    template <typename... Args>
    void warn(const char *fmt, Args &&...args)
    {
        Logger::Inst().Info(LogType::_warning_, fmt, std::forward<Args>(args)...);
    };
    template <typename... Args>
    void error(const char *fmt, Args &&...args)
    {
        Logger::Inst().Info(LogType::_error_, fmt, std::forward<Args>(args)...);
    };
    template <typename... Args>
    void debug(const char *fmt, Args &&...args)
    {
        Logger::Inst().Info(LogType::_debug_, fmt, std::forward<Args>(args)...);
    };
};